Skip to content

Fix Agent close hanging#934

Open
JoTurk wants to merge 1 commit into
mainfrom
jo/fix-agent-close-hang
Open

Fix Agent close hanging#934
JoTurk wants to merge 1 commit into
mainfrom
jo/fix-agent-close-hang

Conversation

@JoTurk

@JoTurk JoTurk commented Jun 30, 2026

Copy link
Copy Markdown
Member

Description

Abort started candidate i/o before waiting for the task loop to stop.
this prevents close from deadblocking when a task is blocked in WriteTo
This is a fix i have been testing for a few days it's missing regression test and some improvements.

@JoTurk

JoTurk commented Jun 30, 2026

Copy link
Copy Markdown
Member Author

@boks1971 I'll get this improved ready for review + regressions tests by tomorrow.

@codecov

codecov Bot commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.08547% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.10%. Comparing base (3ecc088) to head (24d50e5).

Files with missing lines Patch % Lines
udp_mux.go 54.68% 20 Missing and 9 partials ⚠️
candidate_base.go 68.42% 3 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #934      +/-   ##
==========================================
- Coverage   88.33%   88.10%   -0.23%     
==========================================
  Files          46       46              
  Lines        5973     6071      +98     
==========================================
+ Hits         5276     5349      +73     
- Misses        482      496      +14     
- Partials      215      226      +11     
Flag Coverage Δ
go 88.10% <70.08%> (-0.23%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@JoTurk JoTurk force-pushed the jo/fix-agent-close-hang branch 2 times, most recently from 82125a2 to 5168f71 Compare July 6, 2026 04:13
@JoTurk JoTurk marked this pull request as ready for review July 6, 2026 04:14
@JoTurk JoTurk requested review from boks1971 and cnderrauber July 6, 2026 04:14
@JoTurk JoTurk force-pushed the jo/fix-agent-close-hang branch from 5168f71 to 57a11b7 Compare July 6, 2026 04:15
@JoTurk

JoTurk commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

@boks1971 the new tests in agent_test deadblock on main. which confirms the fix i think also with the results from your dev env.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses an Agent shutdown deadlock by aborting started candidate socket I/O before waiting for the task loop’s active task to return, preventing Close() from hanging when a task is blocked in WriteTo.

Changes:

  • Add CloseWithPreStop to taskloop.Loop and use it from Agent.close() to run a pre-stop hook before waiting for the loop to stop.
  • Track started candidates in Agent and abort their I/O during shutdown to unblock in-flight socket operations.
  • Add abort-write plumbing for muxed/shared packet conns and introduce regression tests for blocked candidate writes.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
udp_muxed_conn.go Adds abortWrite() forwarding to the UDP mux to help unblock muxed writes during shutdown.
udp_mux.go Implements abortWrite() via write deadlines to interrupt blocked WriteTo on the shared UDP socket.
shared_packet_conn.go Adds abortWrite() passthrough when the underlying conn supports write abortion.
internal/taskloop/taskloop.go Introduces CloseWithPreStop and sync.Once to ensure close/preStop are executed once safely.
internal/taskloop/taskloop_test.go Adds a concurrency test for CloseWithPreStop behavior (currently contains a compile issue).
candidate_base.go Refactors candidate close into abortIO() + wait, and adds write-abort support via writeAborter.
agent.go Tracks started candidates and aborts their I/O as a pre-stop hook during agent shutdown.
agent_test.go Adds regression tests ensuring Agent close unblocks blocked writes (direct, shared, and UDP mux).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +79 to +89
var wg sync.WaitGroup
wg.Add(closers)
for range closers {
go func() {
defer wg.Done()
loop.CloseWithPreStop(func() {
preStopCalls.Add(1)
})
closeReturned <- struct{}{}
}()
}

@JoTurk JoTurk Jul 6, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

really?

Comment thread udp_mux.go Outdated
Comment thread agent_test.go

@cnderrauber cnderrauber left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thank you!!

@boks1971 boks1971 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

But, the CoPilot flagged UDPMux seemed like something legit?

close(releaseBlock)
wg.Wait()

assert.Equal(t, int32(1), preStopCalls.Load())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a neat test!!!

@JoTurk

JoTurk commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

@boks1971

But, the CoPilot flagged UDPMux seemed like something legit?

Yeah I'm adding an atomic counter.

Abort started candidate i/o before waiting for the task
loop to stop.
this prevents close from deadblocking when a task is blocked in
WriteTo
@JoTurk JoTurk force-pushed the jo/fix-agent-close-hang branch from 57a11b7 to 24d50e5 Compare July 6, 2026 04:55
@JoTurk JoTurk requested a review from Copilot July 6, 2026 04:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Comment thread udp_mux.go
Comment thread udp_mux.go
@JoTurk

JoTurk commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

@boks1971 i added the atomic counter, can you please review / test this again, i'll manually test this a lot before i merge it.

@boks1971

boks1971 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@boks1971 i added the atomic counter, can you please review / test this again, i'll manually test this a lot before i merge it.

Thank you @JoTurk . The changes lgtm! The Gosched calls are always a bit scary. Guess the alternate is not very useful here. Would be good to get 👀 from @cnderrauber again as he has spent a bunch of time in these mux modules.

My set up seems to be in a flux state currently. I will have to try and repro the failure I had before. But, please do not wait for me. I will report back issues when I get a chance to try.

@cnderrauber

cnderrauber commented Jul 6, 2026

Copy link
Copy Markdown
Member

Is there a problem if we just left udpmux unchanged (or empty implementation of abortWrite)? The udp write will not be blocked, so we don't need to abort the write ?

@boks1971

boks1971 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Is there a problem if we just left udpmux unchanged (or empty implementation of abortWrite)? The udp write will not be blocked, so we don't need to abort the write ?

The original theory that forced these set of changes from Jo was that the STUN ping got blocked on a close of an ICE agent. Looks like golang can block UDP write, but has to be very specific conditions like kernel buffer exhaustion which I think should be very rare. But, that was the best theory for hangs in LiveKit server on participant close (especially participants who were not disconnecting cleanly).

@cnderrauber

Copy link
Copy Markdown
Member

Sounds good. I have been concerned the loop hang on close when the write is blocked before, but thought the udp socket would not be blocked. The change looks good to me!

@JoTurk

JoTurk commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

The original theory that forced these set of changes from Jo was that the STUN ping got blocked on a close of an ICE agent. Looks like golang can block UDP write, but has to be very specific conditions like kernel buffer exhaustion which I think should be very rare.

Yes I tried to reproduce it with stressing writes, and I think it has to do with OS buffer, and go handling of EAGAIN, I think it's a bug at the Go runtime, there are a few reports that I found golang/go#73919 golang/go#61555 I'm not sure why it happens more on darwin but i was able to replicate it once on linux.
I'll make that this doesn't cause a regression before i merge it and I'll look into fixing it at the go runtime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants